﻿package com.reyco1.manager
{
	/*
	imports
	*/
	import com.reyco1.extension.StandardExtension;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.utils.Dictionary;
	/*
	class
	*/
	
	/**
	 * Based on code written by voidelement.
	 */
	public class DepthManager extends StandardExtension
	{
		/*
		variables
		*/
		private const HIGHEST_DEPTH:int = 2130690045;
		private const LOWEST_DEPTH:int = -16383;
		private var objContainer:DisplayObjectContainer;
		private var objDictionary:Dictionary;
		private var strName:String;
		/*
		constructor
		*/
		
		/**
		 * DepthManager construnctor
		 * @param container : DisplayIbject whose depth you need to control/manage
		 * 
		 */		
		public function DepthManager(container:DisplayObjectContainer):void 
		{
			this.objContainer = container;
			this.strName = container.name;
			this.objDictionary = new Dictionary(true);
			this.configureDepthManager();
		}
		
		/*
		configure the depth manager
		*/
		private function configureDepthManager():void
		{
			var numOfChildren:int = objContainer.numChildren;
			var objChild:DisplayObject;

			for (var a:int = 0; a < numOfChildren; ++a) {
				objChild = this.objContainer.getChildAt(a);
				this.objDictionary[a] = objChild;
				this.objDictionary[objChild] = a;
			}
			
			output("container: "+this.strName)
			output("current number of objects collected during configuration: "+this.objContainer.numChildren)
		}
		/*
		private fucntions
		*/
		private function searchIndex(depth:int):int 
		{
			if (this.objContainer.numChildren > 0){
				
				var numLowestRegisteredDepth:int = 0;
				var numHighestRegisteredDepth:int = this.objContainer.numChildren - 1;
				var numIndex:int = numHighestRegisteredDepth;
				var child:DisplayObject = this.objContainer.getChildAt(numIndex);

				if (depth <= this.objDictionary[this.objContainer.getChildAt(0)]){
					return 0;
				}
				
				if (objDictionary[child] < depth){
					return numIndex + 1;
				}
				
				while (this.objDictionary[child] != depth){
					if (numIndex == numLowestRegisteredDepth){
						numIndex += this.objDictionary[child] < depth ? 1 : 0;
						break;
					} else if (depth < this.objDictionary[child]){
						numHighestRegisteredDepth = numIndex;
						numIndex = numLowestRegisteredDepth + numHighestRegisteredDepth >> 1;
					} else if (this.objDictionary[child] < depth){
						numLowestRegisteredDepth = numIndex;
						numIndex = numLowestRegisteredDepth + numHighestRegisteredDepth >> 1;
					} else {
						break;
					}
					child = this.objContainer.getChildAt(numIndex);
				}
				
				return numIndex;
				
			} else {
				return 0;
			}
		}
		/*
		add a display object to the Depth Manager instance
		*/
		
		/**
		 * Adds a DisplayObject to the container specified in the DepthManager constructor and registers it in the DepthManager itself.
		 * @param child : any DisplayObject
		 * @return : a reference to the DisplayObject
		 * 
		 */		
		public override function addChild(child:DisplayObject):DisplayObject 
		{
			var depth:int
			
			if(this.objContainer.numChildren){
				depth = this.objDictionary[this.objContainer.getChildAt(this.objContainer.numChildren - 1)];
			}else{
				depth = 0;
			}
			
			this.objDictionary[depth] = child;
			this.objDictionary[child] = depth;
			
			output(child.name+" added to "+this.strName+".")
			
			return this.objContainer.addChild(child);
		}
		
		/*
		add a display object to the depth manager at a specific depth
		*/
		
		/**
		 * Adds a Display Object at a specific depth
		 * @param child : Any DisplayObject
		 * @param depth : depth
		 * @return : a reference to the DisplayObject
		 * @see #addChild
		 * 
		 */		
		public override function addChildAt(child:DisplayObject, depth:int):DisplayObject
		{
			var numIndex:int = this.searchIndex(depth);

			if (numIndex < this.objContainer.numChildren) {
				var otherChild:DisplayObject = this.objContainer.getChildAt(numIndex);

				if (this.objDictionary[otherChild] == depth) {
					removeChild(otherChild);
				}
			}
			
			this.objDictionary[depth] = child;
			this.objDictionary[child] = depth;

			return this.objContainer.addChildAt(child, numIndex);
		}
		
		/*
		removes a display object from the depth manager and from the container as well
		*/
		
		/**
		 * Removes a child from the container and from the DepthManager
		 * @param child : display object to remove
		 * @return : a reference ot the display object
		 * 
		 */		
		public override function removeChild(child:DisplayObject):DisplayObject 
		{
			delete this.objDictionary[this.objDictionary[child]];
			delete this.objDictionary[child];

			return this.objContainer.removeChild(child);
		}
		/*
		removes a display object from the depth manager at a specified depth and from the container as well
		*/
		
		/**
		 * Remove a display object from a specified depth
		 * @param depth : depth from which to remove display object
		 * @return : a reference to the display object
		 * 
		 */		
		public override function removeChildAt(depth:int):DisplayObject 
		{
			var child:DisplayObject = this.objDictionary[depth];

			if (child == null) {
				throw new RangeError("Caution: no child at depth " + depth);
			}
			
			delete this.objDictionary[child];
			delete this.objDictionary[depth];

			return this.objContainer.removeChild(child);
		}
		
		/*
		find a display object in the depth manager and set its depth to the specified depth
		*/
		
		/**
		 * Sets the depth of a display object already added to the depth manager
		 * @param child : display object already in depth manager
		 * @param depth : new depth
		 * 
		 */		
		public function setDepth(child:DisplayObject, depth:int):void 
		{
			if (depth < LOWEST_DEPTH || depth > HIGHEST_DEPTH) {
				throw new Error("Error: " + depth + " is over the highest possible depth.");
			}
			
			if (this.objDictionary[child] == depth) {
				return;
			}
			
			var otherChild:DisplayObject = this.getInstanceAtDepth(depth);

			if (otherChild != null) {
				this.swapChildren(child, otherChild);
			} else {
				var numIndex:int = this.searchIndex(depth);

				delete this.objDictionary[this.objDictionary[child]];
				this.objDictionary[child] = depth;
				this.objDictionary[depth] = child;

				if (this.objContainer.getChildIndex(child) < numIndex) {
					this.objContainer.setChildIndex(child, numIndex - 1);
				} else {
					this.objContainer.setChildIndex(child, numIndex);
				}
			}
		}
		/*
		get display object at depths
		*/
		
		/**
		 * Returns a reference to the display object at the provided depth
		 * @param depth : depth to get display object from
		 * @return : DisplayObject
		 * 
		 */		
		public function getInstanceAtDepth(depth:int):DisplayObject 
		{
			return this.objDictionary[depth];
		}
		/*
		returns the depth of the depth of the provided display object
		*/
		
		/**
		 * Gets the depth of the provided display object
		 * @param child : display object to get depth for
		 * @return : depth
		 * 
		 */		
		public function getDepth(child:DisplayObject):int 
		{
			return this.objDictionary[child];
		}
		/*
		swpaps the depths of the two specified display objects
		*/
		
		/**
		 * swaps the depths of the two display objects provided
		 * @param child1 : display obejct
		 * @param child2 : display obejct
		 * 
		 */		
		public override function swapChildren(child1:DisplayObject, child2:DisplayObject):void 
		{
			if (child1 == child2) {
				return;
			}
			
			var depth:int = this.objDictionary[child1];
			this.objDictionary[child1] = this.objDictionary[child2];
			this.objDictionary[child2] = depth;

			this.objDictionary[this.objDictionary[child1]] = child1;
			this.objDictionary[this.objDictionary[child2]] = child2;

			this.objContainer.swapChildren(child1, child2);
		}
	}
}